﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using HighDiveAdmin.Handlers;

namespace HighDiveAdmin.Resources
{
    /// <summary>
    /// Class for calculateing the difficultydegree using a divecode
    /// </summary>
    public class DiveCodeDifficultyCalculator
    {
        // "01", 0 = number of half somersaults, 1 = dive height
        private Dictionary<string, double> _dictionaryComponentA = new Dictionary<string, double>();
        // "01C", 0 = nmber of half somersaults, 1 = type of dive, C = type of somersault
        private Dictionary<string, double> _dictionaryComponentB = new Dictionary<string, double>();
        // "01", 0 = number of half somersaults, 1 = type of dive
        private Dictionary<string, double> _dictionaryComponentBFlying = new Dictionary<string, double>();
        // "123", 1 = number of twists, 2 = number of half somersaults, 3 = type of dive
        private Dictionary<string, double> _dictionaryComponentC = new Dictionary<string, double>();
        // "1110", 1 = number of half somersaults, 1 = type of dive, 10 = dive height
        private Dictionary<string, double> _dictionaryComponentDNoArmstand = new Dictionary<string, double>();
        // "06", 1 = number of half somersaults, 6 = type of dive
        private Dictionary<string, double> _dictionaryComponentDArmstand = new Dictionary<string, double>();
        // "21", 2 = number of halfsomersaults, 1 = typ of somersault
        private Dictionary<string, double> _dictionaryComponentE = new Dictionary<string, double>();

        private static DiveCodeDifficultyCalculator _instance = new DiveCodeDifficultyCalculator();
        public static DiveCodeDifficultyCalculator Instance { get { return _instance; } }
        private DiveCodeDifficultyCalculator()
        {
            _dictionaryComponentA = XmlHandler.LoadDictionary("dictionaryComponentA");
            _dictionaryComponentB = XmlHandler.LoadDictionary("dictionaryComponentB");
            _dictionaryComponentBFlying = XmlHandler.LoadDictionary("dictionaryComponentBFlying");
            _dictionaryComponentC = XmlHandler.LoadDictionary("dictionaryComponentC");
            _dictionaryComponentDNoArmstand = XmlHandler.LoadDictionary("dictionaryComponentDNoArmstand");
            _dictionaryComponentDArmstand = XmlHandler.LoadDictionary("dictionaryComponentDArmstand");
            _dictionaryComponentE = XmlHandler.LoadDictionary("dictionaryComponentE");
        }

        /// <summary>
        /// Free position only allowed in twisting dives
        /// </summary>
        /// <param name="diveCode">The dive code for the jump</param>
        public void FreePositionOnlyInTwistingDives(string diveCode)
        {
            if (diveCode.Last() == 'D')
            {
                if (diveCode.First() != '5' && !(diveCode.First() == '6' && diveCode.Length == 5))
                {
                    throw new DiveCodeException("Free position only allowed in twisting dives");
                }
            }
        }

        /// <summary>
        /// No rotation armstand dives must have divecode 600A and height >= 5
        /// </summary>
        /// <param name="diveCode">The dive code for the jump</param>
        public void ArmstandNoRotationConstraint(string diveCode)
        {
            if (diveCode.First() == '6' && diveCode.Length == 4)
            {
                if (diveCode[1] == '0' || diveCode[2] == '0')
                {
                    if (diveCode[1] == '0' && diveCode[2] == '0')
                    {
                        if (diveCode.Last() != 'A')
                        {
                            throw new DiveCodeException("This jump can only be performed in position A!");
                        }
                    }
                    else
                    {
                        throw new DiveCodeException("Second and third digit must be 0");
                    }
                }
            }
            
        }

        /// <summary>
        /// Dive with 1 half somersault and twists must be done in position A, B or C
        /// </summary>
        /// <param name="diveCode">The dive code for the jump</param>
        public void HalfSomersaultAndTwistingConstraint(string diveCode)
        {
            if (diveCode.First() == '5' || (diveCode.First() == '6' && diveCode.Length == 5))
            {
                if (diveCode[2] == '1')
                {
                    if (diveCode.Last() == 'D')
                    {
                        throw new DiveCodeException("This jump can only be performed in position A, B or C");
                    }
                }
            }
        }

        /// <summary>
        /// Twisting dives with 1 to 1 & 1/2 somersaults can only be done in position D
        /// </summary>
        /// <param name="diveCode">The dive code for the jump</param>
        public void OneToOneAndAHalfSomersaultAndTwistingConstraint(string diveCode)
        {
            if (diveCode.First() == '5' || (diveCode.First() == '6' && diveCode.Length == 5))
            {
                if (diveCode[2] == '2' || diveCode[2] == '3')
                {
                    if (diveCode.Last() != 'D')
                    {
                        throw new DiveCodeException("This jump can only be performed in position D");
                    }
                }
            }
        }

        /// <summary>
        /// Twisting dives with two or more somersaults can only be done in position B or C
        /// </summary>
        /// <param name="diveCode">The dive code for the jump</param>
        public void TwoOrMoreSomersaultsAndTwistingConstraint(string diveCode)
        {
            if (diveCode.First() == '5' || (diveCode.First() == '6' && diveCode.Length == 5 && Convert.ToInt16(diveCode[2].ToString()) > 4))
            {
                int numberOfHalfSomersaults = Convert.ToInt16(diveCode[2].ToString());
                if (numberOfHalfSomersaults >= 4)
                {
                    if (diveCode.Last() != 'B' && diveCode.Last() != 'C')
                    {
                        throw new DiveCodeException("This jump can only be performed in position B or C");
                    }
                }
            }
        }

        /// <summary>
        /// Armstand twisting dives with one to two somersaults can only be done in position D
        /// </summary>
        /// <param name="diveCode">The dive code for the jump</param>
        public void ArmstandOneToTwoSomersaultsAndTwistingConstraint(string diveCode)
        {
            if (diveCode.First() == 6 && diveCode.Length == 4)
            {
                int numberOfHalfSomersaults = Convert.ToInt16(diveCode[2].ToString());
                if (numberOfHalfSomersaults >= 2 && numberOfHalfSomersaults <= 4)
                {
                    if (diveCode.Last() != 'D')
                    {
                        throw new DiveCodeException("This jump can only be performed in position D");
                    }
                }
            }
        }

        private void CheckSpecialDiveConditions(string diveCode, int diveHeight)
        {
            FreePositionOnlyInTwistingDives(diveCode);
            ArmstandNoRotationConstraint(diveCode);
            HalfSomersaultAndTwistingConstraint(diveCode);
            OneToOneAndAHalfSomersaultAndTwistingConstraint(diveCode);
            TwoOrMoreSomersaultsAndTwistingConstraint(diveCode);
        }

        /// <summary>
        /// Calculates the difficulty degree of a jump.
        /// The try/catch blocks around each component makes sure that any jumps passing the constraints still get a score (0)
        /// even if they do not exist in the component dictionaries.
        /// </summary>
        /// <param name="diveCode">The dive code for the jump</param>
        /// <param name="diveHeight">the height the dive is performed from</param>
        /// <returns>The difficulty degree</returns>
        public double GetDifficulty(string diveCode, int diveHeight)
        {
            diveCode = diveCode.ToUpper();
            CheckSpecialDiveConditions(diveCode, diveHeight);

            double difficultySum = 0;

            // Calculate the degree of difficulty in Component A
            try
            {
                difficultySum += _dictionaryComponentA[diveCode[2] + diveHeight.ToString()];
            }
            catch { }

            // Calculate the degree of difficulty in Component B
            try
            {
                if (diveCode.First() != '5')
                {
                    difficultySum += _dictionaryComponentB[diveCode[2].ToString() + diveCode.First() + diveCode.Last()];
                }
                else //diveCode.First() == 5
                {
                    difficultySum += _dictionaryComponentB[diveCode[2].ToString() + diveCode[1] + diveCode.Last()];
                }
            }
            catch { }

            // Calculate the degree of difficulty in Component B (Flying dives)
            try
            {
                if (diveCode.First() >= '1' && diveCode.First() <= '4' && diveCode[1] == '1')
                {
                    difficultySum += _dictionaryComponentBFlying[diveCode[2].ToString() + diveCode.First()];
                }
            }
            catch { }

            // Calculate the degree of difficulty in Component C (Twisting)
            try
            {
                if (diveCode.First() == '5')
                {
                    difficultySum += _dictionaryComponentC[diveCode[3].ToString() + diveCode[2] + diveCode[1]];
                }
                else if (diveCode.First() == '6' && diveCode.Length == 5)
                {
                    if (diveCode[1] == '1')
                    {
                        difficultySum += _dictionaryComponentC[diveCode[3].ToString() + diveCode[2] + '6'];
                    }
                    else if (diveCode[1] == '2' || diveCode[1] == '3')
                    {
                        difficultySum += _dictionaryComponentC[diveCode[3].ToString() + diveCode[2] + '7'];
                    }
                }
            }
            catch { }

            // Calculate the degree of difficulty in Component D
            try
            {
                if (diveCode.First() == '6' && diveCode.Length == 4)
                {
                    difficultySum += _dictionaryComponentDArmstand[diveCode[2].ToString() + diveCode[1]];
                }
                else if (diveCode.First() == '5')
                {
                    difficultySum += _dictionaryComponentDNoArmstand[diveCode[2].ToString() + diveCode[1] + diveHeight.ToString()];
                }
                else if (diveCode.First() >= '1' && diveCode.First() <= '4')
                {
                    difficultySum += _dictionaryComponentDNoArmstand[diveCode[2].ToString() + diveCode.First() + diveHeight.ToString()];
                }
            }
            catch { }

            // Calculate the degree of difficulty in Component E
            try
            {
                if (diveCode.First() >= '1' && diveCode.First() <= '4')
                {
                    difficultySum += _dictionaryComponentE[diveCode[2].ToString() + diveCode.First()];
                }
                else if (diveCode.First() == '6' && diveCode.Length == 4)
                {
                    if (diveCode[1] == '1')
                    {
                        difficultySum += _dictionaryComponentE[diveCode[2].ToString() + '6'];
                    }
                    else if (diveCode[1] == '2' || diveCode[1] == '3')
                    {
                        difficultySum += _dictionaryComponentE[diveCode[2].ToString() + '7'];
                    }
                }
            }
            catch { }

            return difficultySum;
        }
    }
}
